// Skeleton
// (c) jimon game studio

#ifndef JEH_JESKELETON
#define JEH_JESKELETON

#include "jeUnknown.h"
#include "jeStl.h"

namespace je
{
	namespace scene
	{
		//! Skeleton Bone
		class jeSkeletonBone:public je::jeUnknown
		{
		public:
			//! Animation Key
			struct jeAnimationKey
			{
				//! Frame
				u32 Frame;

				//! Position
				jeVector3df Position;

				//! Scale
				jeVector3df Scale;

				//! Orientation
				jeQuaternionf Orientation;
			};
		protected:
			//! Bone Id
			u32 BoneId;

			//! Parent Bone
			jeSkeletonBone * Parent;

			//! Bone Childrens
			typedef jeDynamicArray<jeSkeletonBone*> ChildrensType;
			ChildrensType Childrens;

			//! Keys Array
			typedef jeDynamicArray<jeAnimationKey*> KeysType;
			KeysType Keys;

			//! Bind Pose Local Matrix
			jeMatrix4f BindPoseLocalMatrix;

			//! Bind Pose Global Matrix Inverted
			jeMatrix4f BindPoseGlobalMatrixInverted;

			//! Current Local Bone Matrix
			jeMatrix4f CurrentLocalBoneMatrix;

			//! Bone Global Matrix
			jeMatrix4f BoneGlobalMatrix;

			//! Bone Global Normal Matrix
			jeMatrix4f BoneGlobalNormalMatrix;
		public:
			//! Constructor
			jeSkeletonBone(const jeMatrix4f & BindPoseLocal)
				:BoneId(0),Parent(NULL),BindPoseLocalMatrix(BindPoseLocal)
			{
			}
			
			//! Destructor
			virtual ~jeSkeletonBone()
			{
				for(u32 i=0;i<Keys.GetSize();i++)
					JEDELETE(Keys[i])
				Keys.Clear();

				for(u32 i=0;i<Childrens.GetSize();i++)
					Childrens[i]->Drop();
				Childrens.Clear();
			}

			//! Set Bone Id
			//! must be unique for skeleton, must be >= 1
			void SetBoneId(u32 Id)
			{
				if(Id < 1)
					JELOG_E("Wrong bone id")

				BoneId = Id;
			}

			//! Get Bone Id
			u32 GetBoneId()
			{
				return BoneId;
			}

			//! Set Parent
			void SetParent(jeSkeletonBone * NewParent)
			{
				Parent = NewParent;
			}

			//! Get Parent
			jeSkeletonBone * GetParent()
			{
				return Parent;
			}

			//! Set Bone Bind Pose Local Matrix
			void SetBoneBindPoseLocalMatrix(const jeMatrix4f & BindPoseLocal)
			{
				BindPoseLocalMatrix = BindPoseLocal;
			}

			//! Get Bone Global Matrix
			const jeMatrix4f & GetBoneGlobalMatrix()
			{
				return BoneGlobalMatrix;
			}

			//! Get Bone Global Normal Matrix
			const jeMatrix4f & GetBoneGlobalNormalMatrix()
			{
				return BoneGlobalNormalMatrix;
			}

			//! Calculate Bind Pose Global Matrixes
			void CalculateBindPoseGlobalMatrixes(const jeMatrix4f & ParentMatrix = jeMatrix4f())
			{
				jeMatrix4f Temp = ParentMatrix * BindPoseLocalMatrix;
				Temp.GetInverse(BindPoseGlobalMatrixInverted,true);
				
				for(u32 i=0;i<Childrens.GetSize();i++)
					Childrens[i]->CalculateBindPoseGlobalMatrixes(Temp);
			}

			//! Calculate Hierarchy Matrixes
			//! Frames :
			//! 0...1.x.2...3...4...5
			//! there current Frame means 1, and time means distance from 1 to 2, time = 0 it's 1, time = 1 it's 2
			void CalculateHierarchyMatrixes(u32 CurrentFrame,f32 Time,u1 EnableInterpolation = true,const jeMatrix4f & ParentMatrix = jeMatrix4f())
			{
				u32 KeyId = 0;
				u1 KeyFinded = false;

				if((CurrentFrame < Keys.GetSize()) && (Keys[CurrentFrame]->Frame == CurrentFrame))
				{
					KeyId = CurrentFrame;
					KeyFinded = true;
				}

				if(!KeyFinded)
				{
					//u32 i = 0;
					//u32 j = Keys.GetSize()-1;

					//while(i<j)
					//{
					//	u32 k = (i+j)/2; // TODO hmmm
					//	if(CurrentFrame >= Keys[k]->Frame)
					//		i = k+1;
					//	else
					//		j = k;
					//	//JELOG(jeStringc(i)+jeStringc(" --- ")+jeStringc(j))
					//}

					//KeyId = j;

					// TODO

					if(CurrentFrame < Keys[0]->Frame)
						CurrentFrame = Keys[0]->Frame;

					for(u32 i=0;i<(Keys.GetSize()-1);i++)
					{
						if((Keys[i]->Frame <= CurrentFrame) && (Keys[i+1]->Frame >= CurrentFrame))
						{
							KeyId = i;
							break;
						}
					}

					if(CurrentFrame > Keys[Keys.GetSize()-1]->Frame)
						KeyId = Keys.GetSize()-1;
				}

				jeVector3df Position;
				jeVector3df Scale;
				jeQuaternionf Orientation;


				if(KeyId < Keys.GetSize() - 1)
				{
					jeAnimationKey * Key1 = Keys[KeyId];
					jeAnimationKey * Key2 = Keys[KeyId+1];

					u32 FramesCount = Key2->Frame - Key1->Frame;
					u32 CurrentLocalFrame = CurrentFrame - Key1->Frame;

					JEASSERT(CurrentFrame >= Key1->Frame) // TODO check this
					JEASSERT(CurrentFrame <= Key2->Frame)

					if(EnableInterpolation)
					{
						f32 WorkTime = ((f32)CurrentLocalFrame + Time) / (f32)FramesCount;
						Position = Key1->Position.LinearInterpolation(Key2->Position,WorkTime);
						Scale = Key1->Scale.LinearInterpolation(Key2->Scale,WorkTime);
						Orientation = Key1->Orientation.Slerp(Key2->Orientation,WorkTime);
					}
					else
					{
						Position = Key1->Position;
						Scale = Key1->Scale;
						Orientation = Key1->Orientation;
					}
				}
				else
				{
					jeAnimationKey * LastKey = Keys[Keys.GetSize()-1];
					Position = LastKey->Position;
					Scale = LastKey->Scale;
					Orientation = LastKey->Orientation;
				}

				CurrentLocalBoneMatrix.Identity();
				CurrentLocalBoneMatrix.Scale(Scale);
				CurrentLocalBoneMatrix = Orientation.GetMatrix() * CurrentLocalBoneMatrix;
				CurrentLocalBoneMatrix.Translate(Position);
				
				jeMatrix4f TempBoneMatrix = ParentMatrix * CurrentLocalBoneMatrix;
				BoneGlobalMatrix = TempBoneMatrix * BindPoseGlobalMatrixInverted;

				BoneGlobalNormalMatrix = BoneGlobalMatrix.GetNormalMatrix();

				for(u32 i=0;i<Childrens.GetSize();i++)
					Childrens[i]->CalculateHierarchyMatrixes(CurrentFrame,Time,EnableInterpolation,TempBoneMatrix);
			}

			//! Get Childrens
			ChildrensType & GetChildrens()
			{
				return Childrens;
			}

			//! Get Keys
			KeysType & GetKeys()
			{
				return Keys;
			}
		};

		//! Skeleton Animation
		class jeSkeletonAnimation:public je::jeUnknown
		{
		protected:
			//! Start Frame
			u32 StartFrame;

			//! End Frame
			u32 EndFrame;

			//! Fps
			f32 Fps;
		public:
			//! Constructor
			jeSkeletonAnimation(u32	Start,u32 End,f32 Speed)
				:StartFrame(Start),EndFrame(End),Fps(Speed)
			{
			}

			//! Destructor
			virtual ~jeSkeletonAnimation()
			{
			}

			//! Set Start Frame
			inline void SetStartFrame(u32 Start)
			{
				StartFrame = Start;
			}

			//! Get Start Frame
			inline u32 GetStartFrame()
			{
				return StartFrame;
			}

			//! Set End Frame
			inline void SetEndFrame(u32 End)
			{
				EndFrame = End;
			}

			//! Get End Frame
			inline u32 GetEndFrame()
			{
				return EndFrame;
			}

			//! Set Fps
			inline void SetFps(f32 Speed)
			{
				Fps = Speed;
			}

			//! Get Fps
			inline f32 GetFps()
			{
				return Fps;
			}
		};
	
		//! Skeleton
		class jeSkeleton:public je::jeUnknown
		{
		public:
			typedef jeDynamicArray<jeSkeletonAnimation*> AnimationsType;
			typedef jeDynamicArray<jeSkeletonBone*> BonesMappingIdType;
		protected:
			//! Root Bone
			jeSkeletonBone * RootBone;

			//! Animations
			AnimationsType Animations;

			//! Bones By Id
			BonesMappingIdType BonesId;
		public:
			//! Constructor
			jeSkeleton(jeSkeletonBone * Root)
				:RootBone(Root)
			{
			}
			
			//! Destructor
			virtual ~jeSkeleton()
			{
				BonesId.Clear();

				for(u32 i=0;i<Animations.GetSize();i++)
					Animations[i]->Drop();
				Animations.Clear();

				if(RootBone)
					RootBone->Drop();
			}

			//! Calculate Skeleton
			virtual void CalculateSkeleton(jeSkeletonAnimation * Animation,f32 AnimationTime,u1 EnableInterpolation = true)
			{
				u32 AnimStartFrame = Animation->GetStartFrame();
				f32 AnimFps = Animation->GetFps();

				f32 CurrentFrame = AnimationTime * AnimFps;

				f32 CurrentBaseFrameF = floorf(CurrentFrame);
				u32 CurrentBaseFrame = (u32)CurrentBaseFrameF + AnimStartFrame;
				f32 CurrentBaseFrameTime = CurrentFrame - CurrentBaseFrameF;

				RootBone->CalculateHierarchyMatrixes(CurrentBaseFrame,CurrentBaseFrameTime,EnableInterpolation);
			}

			//! Calculate Skeleton
			virtual void CalculateSkeletonId(u32 AnimationId,f32 AnimationTime,u1 EnableInterpolation = true)
			{
				CalculateSkeleton(Animations[AnimationId],AnimationTime,EnableInterpolation);
			}

			//! Get Root Bone
			virtual jeSkeletonBone * GetRootBone()
			{
				return RootBone;
			}

			//! Get Animations
			AnimationsType & GetAnimations()
			{
				return Animations;
			}

			//! Get Bone By Id, bone id = 0 means no bone !
			jeSkeletonBone * GetBoneById(u32 BoneId)
			{
				if(!BoneId)
					return NULL;

				BoneId = BoneId - 1;
				if(BoneId < BonesId.GetSize())
					return BonesId[BoneId];
				else
					return NULL;
			}

			//! Get Bones Mapping
			BonesMappingIdType & GetBonesMapping()
			{
				return BonesId;
			}
		};
	}
}

#endif